<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
   
  <meta name="keywords" content="Java Python" />
   
  <meta name="description" content="From Zero to Hero" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    JUC-01-简介 |  朱酱酱的学习博客
  </title>
  <meta name="generator" content="hexo-theme-yilia-plus">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/css/style.css">

  
<script src="/js/pace.min.js"></script>


  

  

<link rel="alternate" href="/atom.xml" title="朱酱酱的学习博客" type="application/atom+xml">
</head>

</html>

<body>
  <div id="app">
    <main class="content">
      <section class="outer">
  <article id="post-JUC/JUC-01-简介与锁升级" class="article article-type-post" itemscope
  itemprop="blogPost" data-scroll-reveal>

  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  JUC-01-简介
</h1>
  

    </header>
    

    
    <div class="article-meta">
      <a href="/2020/04/17/JUC/JUC-01-%E7%AE%80%E4%BB%8B%E4%B8%8E%E9%94%81%E5%8D%87%E7%BA%A7/" class="article-date">
  <time datetime="2020-04-17T04:02:24.000Z" itemprop="datePublished">2020-04-17</time>
</a>
      
      
      
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">3.6k字</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">14分钟</span>
        </span>
    </span>
</div>

      
    </div>
    

    
    
    <div class="tocbot"></div>





    

    <div class="article-entry" itemprop="articleBody">
      


      

      
      <h1 id="JUC-01-简介"><a href="#JUC-01-简介" class="headerlink" title="JUC-01-简介"></a>JUC-01-简介</h1><h2 id="1-JUC-简介"><a href="#1-JUC-简介" class="headerlink" title="1. JUC 简介"></a>1. JUC 简介</h2><ul>
<li><strong>JUC即java.util.concurrent(java的工具包)</strong></li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200416/220702037.png" alt="mark"></p>
<a id="more"></a>

<p>JUC常用的几个类：</p>
<ul>
<li>lock接口</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200416/221107348.png" alt="mark"></p>
<ul>
<li>Callable接口</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200416/221208393.png" alt="mark"></p>
<h3 id="1-1-线程和进程"><a href="#1-1-线程和进程" class="headerlink" title="1.1 线程和进程"></a>1.1 线程和进程</h3><p><strong>进程：程序</strong></p>
<ul>
<li><strong>一个进程可以包含多个线程，至少包含一个线程</strong></li>
<li><strong>java默认有两个线程（2个 -&gt; GC和main线程）</strong></li>
</ul>
<p>线程：<strong>Java是无法开启线程的，只能通过native去调用</strong></p>
<h3 id="1-2-并发和并行"><a href="#1-2-并发和并行" class="headerlink" title="1.2 并发和并行"></a>1.2 并发和并行</h3><ul>
<li>并发：多线程操作同一个资源（相当于一个CPU）</li>
<li>并行：多个线程可以同时执行  (相当于多个CPU)</li>
</ul>
<p>并发编程的本质：充分利用CPU的资源</p>
<h2 id="2-线程的状态"><a href="#2-线程的状态" class="headerlink" title="2. 线程的状态"></a>2. 线程的状态</h2><ul>
<li>它是一个<code>java.lang.Thread</code>里面的枚举类</li>
</ul>
<p>源码分析：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span> State &#123;</span><br><span class="line">    <span class="comment">// 线程新生</span></span><br><span class="line">    NEW,</span><br><span class="line">    <span class="comment">// 运行</span></span><br><span class="line">    RUNNABLE,</span><br><span class="line">    <span class="comment">// 阻塞</span></span><br><span class="line">    BLOCKED,</span><br><span class="line">    <span class="comment">// 阻塞等待</span></span><br><span class="line">    WAITING,</span><br><span class="line">    <span class="comment">// 超时等待</span></span><br><span class="line">    TIMED_WAITING,</span><br><span class="line">    <span class="comment">// 终止</span></span><br><span class="line">    TERMINATED;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200919/220758566.png" alt="mark"></p>
<h2 id="3-wait-Sleep-的区别"><a href="#3-wait-Sleep-的区别" class="headerlink" title="3. wait() Sleep()的区别"></a>3. wait() Sleep()的区别</h2><ol>
<li><strong>来自不同的类</strong></li>
</ol>
<ul>
<li>wait  来自 Object</li>
<li>sleep 来自 Thread</li>
</ul>
<ol start="2">
<li><strong>关于锁的释放</strong></li>
</ol>
<ul>
<li><p>wait   会释放锁</p>
</li>
<li><p>sleep 不会释放 （抱着锁睡觉）</p>
</li>
</ul>
<ol start="3">
<li><strong>使用的范围</strong></li>
</ol>
<ul>
<li><p>wait     必须在同步代码中</p>
</li>
<li><p>sleep    可以在任何地方使用</p>
</li>
</ul>
<p><strong>java.lang.Thread类原码解析</strong>：<a href="https://blog.csdn.net/pengqiaowolf/article/details/80442071" target="_blank" rel="noopener">https://blog.csdn.net/pengqiaowolf/article/details/80442071</a></p>
<p><a href="https://www.cnblogs.com/rouqinglangzi/p/10803194.html" target="_blank" rel="noopener">https://www.cnblogs.com/rouqinglangzi/p/10803194.html</a></p>
<h2 id="3-Synchronized"><a href="#3-Synchronized" class="headerlink" title="3. Synchronized"></a>3. Synchronized</h2><ul>
<li>这里可以参考博客： <a href="https://blog.csdn.net/baidu_38083619/article/details/82527461" target="_blank" rel="noopener">https://blog.csdn.net/baidu_38083619/article/details/82527461</a></li>
<li>CSDN ： <a href="https://www.cnblogs.com/mingyueyy/p/13054296.html" target="_blank" rel="noopener">https://www.cnblogs.com/mingyueyy/p/13054296.html</a></li>
</ul>
<p><strong>卖票案例：</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.zhuuu;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 线程就是一个单独的资源类，没有任何附属的操作</span></span><br><span class="line"><span class="comment">// 1. 属性，方法</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 基本的卖票例子</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SaleTicket</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 并发：多线程操作同一个额资源</span></span><br><span class="line">        <span class="comment">// 把资源类丢入线程</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// @FunctionalInterface:函数式接口</span></span><br><span class="line">        <span class="comment">// lambada表达式(放参数)-&gt;&#123;代码&#125;</span></span><br><span class="line">        Ticket ticket = <span class="keyword">new</span> Ticket();</span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">60</span>; i++) &#123;</span><br><span class="line">                ticket.sale();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,<span class="string">"A"</span>).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">60</span>; i++) &#123;</span><br><span class="line">                ticket.sale();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,<span class="string">"B"</span>).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">60</span>; i++) &#123;</span><br><span class="line">                ticket.sale();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;,<span class="string">"C"</span>).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 资源类</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Ticket</span></span>&#123;</span><br><span class="line">    <span class="comment">// 属性，方法</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> number = <span class="number">50</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 卖票的方式</span></span><br><span class="line">    <span class="comment">// Sychronized本质：队列+锁</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">sale</span><span class="params">()</span></span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (number &gt; <span class="number">0</span>)&#123;</span><br><span class="line">            System.out.println(Thread.currentThread().getName()+<span class="string">"卖出了"</span>+(number--)+<span class="string">"票，剩余"</span>+number);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="3-1-Synchronized-底层原理分析"><a href="#3-1-Synchronized-底层原理分析" class="headerlink" title="3.1 Synchronized 底层原理分析"></a>3.1 Synchronized 底层原理分析</h3><ul>
<li><strong>JDK 1.6 之前：采用 <code>monitorenter 和 monitorexit</code>  来进行加锁和解锁（以下是Synchronized 的三种加锁方式）</strong><ul>
<li><strong>实例方法 ：  锁的是当前实例对象</strong></li>
<li><strong>同步方法：   锁的是class对象</strong></li>
<li><strong>代码块  ：     锁的是代码块Synchronized () 括号中的对象。</strong></li>
</ul>
</li>
</ul>
<p>注意：通过<code>javap -v</code>来查看对应代码的字节码指令，对于同步块实现同<strong>时使用了<code>monitorenter</code>和<code>monitorexit</code>指令，这样就隐式的执行了 Lock 和  unlock 的操作，用于保证原子性。</strong></p>
<p><code>monitorenter</code>指令插入到同步代码块开始的位置、<code>monitorexit</code>指令插入到同步代码块结束位置</p>
<p>jvm 保证每一个<code>monitorenter</code>和都有一个对应的 <code>monitorexit</code>。</p>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200719/194142707.png" alt="mark"></p>
<ul>
<li>线程执行到<code>monitorenter</code>指令时，会尝试获取对象所对应的monitor所有权，也就是尝试获取对象的锁；</li>
<li>而执行 <code>monitorexit</code>就是释放monitor的所有权。</li>
</ul>
<p><strong>同时 那什么是monitor呢？</strong></p>
<p>1.Monitor是一种用<strong>来实现同步的工具</strong></p>
<p>2.与每个java对象相关联，<strong>所有的 Java 对象是天生携带 monitor</strong></p>
<p>3.<strong>Monitor是实现Sychronized(内置锁)的基础</strong></p>
<p>对象的监视器（monitor）由ObjectMonitor对象实现（C++），其跟同步相关的数据结构如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">ObjectMonitor() &#123;</span><br><span class="line">    _count        = <span class="number">0</span>; <span class="comment">//用来记录该对象被线程获取锁的次数</span></span><br><span class="line">    _waiters      = <span class="number">0</span>;</span><br><span class="line">    _recursions   = <span class="number">0</span>; <span class="comment">//锁的重入次数</span></span><br><span class="line">    _owner        = NULL; <span class="comment">//指向持有ObjectMonitor对象的线程 </span></span><br><span class="line">    _WaitSet      = NULL; <span class="comment">//处于wait状态的线程，会被加入到_WaitSet</span></span><br><span class="line">    _WaitSetLock  = <span class="number">0</span> ;</span><br><span class="line">    _EntryList    = NULL ; <span class="comment">//处于等待锁block状态的线程，会被加入到该列表</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>





<ul>
<li><strong>JDK 1.6 之后：</strong> 引入<strong>锁升级的概念（将锁放在了java对象头中</strong>，分别用00，01，10，11 代表锁的四个状态）（这里只是略带提一嘴，后面会进行详细说明）<ul>
<li>无锁状态 : </li>
<li>偏向锁状态  ：01</li>
<li>轻量级锁状态 ： 00</li>
<li>重量级锁状态  ： 10</li>
</ul>
</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200719/194232145.png" alt="mark"></p>
<ul>
<li><ul>
<li>Mark Word（对象标记）用于存储对象自身的运行时数据，如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等。</li>
<li>Java对象头一般占有两个机器码(在32位虚拟机中，1个机器码等于4字节， 也就是32bit)</li>
</ul>
</li>
</ul>
<h3 id="3-2-锁升级的过程"><a href="#3-2-锁升级的过程" class="headerlink" title="3.2 锁升级的过程"></a>3.2 锁升级的过程</h3><p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200719/194715004.png" alt="mark"></p>
<p>（上图标志位是我从别的博客找来的，图是有问题的，具体问题我会在下面解释，一边深入理解，一边带您发现图中问题在哪里。）</p>
<p>首先我们来<strong>了解相关锁</strong>的概念:</p>
<ul>
<li><p><strong>自旋锁（CAS）</strong> : 让不满足条件的线程等待一会看看能不能获取到锁，通过占用CPU的时间来避免线程带来的上下文切换。</p>
<ul>
<li>自旋锁等待时间和次数是有一个限度的，如果自旋超过了定义的时间仍然没有获取到锁，那么这个线程将被挂起（被阻塞）。</li>
<li>在 <strong>JDK 1.6 之后引入了自适应自旋锁</strong>，自适应意味着自旋的次数不是固定不变的，而是根据前一次在同一锁上自旋的时间以及锁的拥有者状态来决定的。</li>
<li>如果在同一个锁对象上，<strong>自旋等待刚刚成功获得过锁</strong>，并且持有锁的线程正在运行中，那么虚拟机就会认为这个自旋很有可能再次成功，进而它<strong>将允许自旋等待持续相对更长的时间</strong>。</li>
<li>如果对于某个锁，自旋<strong>很少成功获得过锁</strong>，那么在以后尝试获得这个锁的时候可能省略掉自旋的过程，<strong>直接阻塞线程</strong>，避免处理器浪费资源。</li>
</ul>
</li>
<li><p><strong>偏向锁</strong> ： 大多数情况下，锁总是由同一个线程多次获得。当一个线程访问同步块并后去锁的时候，会在对象头和栈帧中<strong>记录存储偏向锁的线程ID</strong></p>
<ul>
<li>偏向锁是一个可重入锁。如果锁对象头的Mark Word 里面存储着当前线程ID的偏向锁，那么就<strong>不需要重新进行CAS</strong>操作来加锁和解锁。</li>
<li>当有其他线程尝试竞争偏向锁的时候，持有偏向锁的线程（不处于活动状态）才会释放锁。</li>
<li><strong>偏向锁无法使用自旋锁进行优化，因为一旦有其他线程竞争锁，就破坏了偏向锁转而升级成轻量级锁。</strong></li>
</ul>
</li>
<li><p><strong>轻量级锁</strong> ： 减少无实际竞争的情况下，使用重量级锁带来的性能消耗。</p>
<ul>
<li>JVM会将现在<strong>当前线程的栈帧</strong>中<strong>创建用于存储锁记录的空间</strong>LockRecord,</li>
<li>将对象头中的Mark word 复制到 lockRecord 中 并将lockRecord 中的 Owner指向锁对象。</li>
<li>然后<strong>线程会尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针</strong>，成功则当前线程获取到锁，如果失败，表示其他线程竞争锁。当前线程使用自旋的方式获取锁，<strong>自旋获取锁失败就会升级成重量级锁。</strong></li>
</ul>
</li>
<li><p><strong>重量级锁</strong> ： 通过<strong>对象内部监视器 （monitor） 实现</strong>，其中 monitor的本质是依赖于底层操作系统<code>Mutex Lock</code>实现。</p>
<ul>
<li>操作系统实现线程之间的切换需要从用户态到内核态的切换，切换成本十分的高。</li>
<li>线程竞争不使用自旋，不会消耗CPU，但是线程会立马进入阻塞状态并等待被其他线程唤醒。</li>
</ul>
</li>
</ul>
<h3 id="3-3-完整的锁升级过程"><a href="#3-3-完整的锁升级过程" class="headerlink" title="3.3 完整的锁升级过程"></a>3.3 完整的锁升级过程</h3><p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200719/204016691.png" alt="mark"></p>
<h3 id="3-4-结合Object-中-wait-notify-notifyall-来认识Synchronized"><a href="#3-4-结合Object-中-wait-notify-notifyall-来认识Synchronized" class="headerlink" title="3.4 结合Object 中 wait,notify,notifyall 来认识Synchronized"></a>3.4 结合Object 中 wait,notify,notifyall 来认识Synchronized</h3><ul>
<li>前面我们在讲 <strong>synchronized 的时候</strong>，发现被阻塞的线程什 么时候被唤醒，取决于获得锁的线程什么时候执行完同步代码块并且释放锁<ul>
<li>那么怎么做到<strong>显示的控制</strong>呢？ 我们就需要借助一个<strong>信号机制</strong></li>
<li><strong>在Object类中 提供了 wait/nofify/notifyall 来控制线程的状态。</strong></li>
</ul>
</li>
</ul>
<ol>
<li><strong>wait()</strong> </li>
</ol>
<ul>
<li>表示持有对象锁的线程A准备<strong>释放对象锁的权限</strong>，释放cpu资源并进入等待状态</li>
</ul>
<ol start="2">
<li><strong>notify()</strong></li>
</ol>
<ul>
<li>表示持有对象锁的<strong>线程A准备释放对象锁的权限，通知 jvm 唤醒某个等待中竞争该锁的线程X.</strong></li>
<li>线程A 执行完 synchronized  代码块并释放了锁之后，线程X直接获取对象锁的权限，其他竞争线程继续等待（即使X同步执行完毕，释放了对象锁，其他竞争线程仍然等待，直到有新的notify和 notifyall被调用）</li>
</ul>
<ol start="3">
<li><strong>notifyAll（）</strong></li>
</ol>
<ul>
<li>notifyAll 和 notify 的区别是 <ul>
<li><strong>notifyall 会唤醒所有竞争等待同一个对象锁的所有线程</strong></li>
<li>当获取锁的线程A被释放了之后，所有被唤醒的线程都可以获得对象的锁权限。</li>
</ul>
</li>
</ul>
<p><strong>注意：</strong></p>
<ul>
<li>三个方法都必须在synchronized 同步关键字所<strong>限定的作用域</strong>中被调用，否则会抛出 <code>java.lang.illegalMonitorStateException</code> 。</li>
<li><strong>意思就是没有同步，所以线程对对象锁的状态是不确定的，不能调用这些方法。</strong></li>
<li>另外，同步机制确保了线程从wait方法返回时能够感知到notify线程对变量做出的修改。</li>
</ul>
<p><strong>这里有一个常见面试题</strong></p>
<ul>
<li><strong>请你说说 wait/notify/notifyall 为什么需要在synchronized里面？</strong></li>
</ul>
<ol>
<li>wait方法的语义有两个，一个是<strong>释放当前的对象锁</strong>，另一个是<strong>使得当前线程进入阻塞队列</strong>，而这些都和监视器相关，所以wait方法必须要获得一个监视器锁。</li>
<li>对于notify来说也一样，它是唤醒一个线程，既然要去唤醒，首先<strong>得知道它在哪里</strong>？所以必须要找到这个对象并且拿到这个对象获取的锁，然后到这个对象等待队列中<strong>去唤醒另外一个线程</strong>。</li>
<li>每个对象可能有多个线程调用wait方法，所以需要有要给<strong>等待队列存贮这些阻塞线程</strong>。这个<strong>等待队列应该和这些对象绑定</strong>，在调用wait 和 notify 方法时也会存在线程安全问题，所以需要一个锁来保证线程安全。</li>
</ol>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200721/111842593.png" alt="mark"></p>
<h2 id="4-Lock-接口"><a href="#4-Lock-接口" class="headerlink" title="4. Lock 接口"></a>4. Lock 接口</h2><ul>
<li><strong>java.util.concurrent.locks 包下</strong></li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200418/143124851.png" alt="mark"></p>
<ul>
<li><p><strong>可重入锁:（ReebtrantLock）</strong></p>
</li>
<li><p><strong>读锁 : (ReentrantReadWtiteLock.ReadLock)</strong></p>
</li>
<li><p><strong>写锁： (ReentrantReadWtiteLock.WriteLock)</strong></p>
</li>
</ul>
<p><img src="http://zhuuu-bucket.oss-cn-beijing.aliyuncs.com/img/20200418/143530777.png" alt="mark"></p>
<ul>
<li><strong>公平锁 ：先来后到</strong> </li>
<li><strong>非公平锁 ： 可以插队（默认）</strong></li>
</ul>
<p><strong>lock锁实现公平卖票</strong></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.zhuuu;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.Lock;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.locks.ReentrantLock;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SaleTicketLock</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 并发：多线程操作同一个额资源</span></span><br><span class="line">        <span class="comment">// 把资源类丢入线程</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// @FunctionalInterface:函数式接口</span></span><br><span class="line">        <span class="comment">// lambada表达式(放参数)-&gt;&#123;代码&#125;</span></span><br><span class="line">        Ticket2 ticket2 = <span class="keyword">new</span> Ticket2();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123; <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">60</span>; i++) &#123; ticket2.sale(); &#125; &#125;,<span class="string">"A"</span>).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123; <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">60</span>; i++) &#123; ticket2.sale(); &#125; &#125;,<span class="string">"B"</span>).start();</span><br><span class="line">        <span class="keyword">new</span> Thread(()-&gt;&#123; <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; <span class="number">60</span>; i++) &#123; ticket2.sale(); &#125; &#125;,<span class="string">"C"</span>).start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 资源类</span></span><br><span class="line"><span class="comment">// Lock锁</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Ticket2</span></span>&#123;</span><br><span class="line">    <span class="comment">// 属性，方法</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span>  number = <span class="number">50</span>;</span><br><span class="line"></span><br><span class="line">    Lock lock = <span class="keyword">new</span> ReentrantLock();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 卖票的方式</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">sale</span><span class="params">()</span></span>&#123;</span><br><span class="line"></span><br><span class="line">        lock.lock(); <span class="comment">// 加锁的操作</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 业务代码</span></span><br><span class="line">            <span class="keyword">if</span> (number &gt; <span class="number">0</span>)&#123;</span><br><span class="line">                System.out.println(Thread.currentThread().getName()+<span class="string">"卖出了"</span>+(number--)+<span class="string">"票，剩余"</span>+number);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            lock.unlock(); <span class="comment">// 解锁</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="5-Sychronized-和-Lock区别"><a href="#5-Sychronized-和-Lock区别" class="headerlink" title="5. Sychronized 和 Lock区别"></a>5. Sychronized 和 Lock区别</h2><ul>
<li><p>Synchronized是一个内置的关键字，lock是一个java类</p>
</li>
<li><p>Synchronized无法判断获取锁的状态，lock可以判断是否获取到了锁</p>
</li>
<li><p>Synchronized会自动释放锁，lock必须手动释放锁，如果不释放锁会造成死锁</p>
</li>
<li><p>Synchronized 线程1（获得锁，阻塞） 线程2 （一直等待）  而lock锁有（try-lock） 不会一直等待下去</p>
</li>
<li><p>Synchronized 可重入锁，不可以中断的，非公平</p>
</li>
<li><p>Lock可重入锁，可以中断，可以公平也可以公平</p>
</li>
<li><p>Synchronized适合锁方法和代码块，lock更适合所大量的代码块</p>
</li>
</ul>
<h2 id="6-start-和-run-方法的区别"><a href="#6-start-和-run-方法的区别" class="headerlink" title="6. start()和 run()方法的区别"></a>6. start()和 run()方法的区别</h2><ul>
<li><strong>用start方法来启动线程，真正实现了多线程运行</strong>，这时无需等待run方法体中的代码执行完毕而直接继续执行后续的代码。<strong>通过调用Thread类的 start()方法来启动一个线程，这时此线程处于就绪（可运行）状态，并没有运行，一旦得到cpu时间片，就开始执行run()方法，这里的run()方法 称为线程体，它包含了要执行的这个线程的内容，Run方法运行结束，此线程随即终止。</strong></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * This method is not invoked for the main method thread or "system"</span></span><br><span class="line"><span class="comment">     * group threads created/set up by the VM. Any new functionality added</span></span><br><span class="line"><span class="comment">     * to this method in the future may have to also be added to the VM.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * A zero status value corresponds to state "NEW".</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">if</span> (threadStatus != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalThreadStateException();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Notify the group that this thread is about to be started</span></span><br><span class="line"><span class="comment">     * so that it can be added to the group's list of threads</span></span><br><span class="line"><span class="comment">     * and the group's unstarted count can be decremented. */</span></span><br><span class="line">    group.add(<span class="keyword">this</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">boolean</span> started = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        start0();</span><br><span class="line">        started = <span class="keyword">true</span>;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">if</span> (!started) &#123;</span><br><span class="line">                group.threadStartFailed(<span class="keyword">this</span>);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (Throwable ignore) &#123;</span><br><span class="line">            <span class="comment">/* do nothing. If start0 threw a Throwable then</span></span><br><span class="line"><span class="comment">              it will be passed up the call stack */</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">native</span> <span class="keyword">void</span> <span class="title">start0</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure>

<p><strong>注意：</strong></p>
<ul>
<li>start()方法才是真是开启了一个新线程</li>
<li>Start（）是启动一个新的线程，<strong>然后新的线程会调用run()方法，但是start()方法不可以重复调用</strong>，若会出现异常Exception in Thread “main” <code>java.lang.IllegalThreadStateException</code>.而且启动线程，会出现异步的效果，即线程创建和启动是随机的</li>
<li>run（）方法类似一个一个普通方法，如果单独调用，仅仅会在当前线程启用，不会重新启动新的线程。启动线程是同步的。</li>
</ul>
<ul>
<li><p>如果该线程是使用独立的 <code>Runnable</code> 运行对象构造的，则调用该 <code>Runnable</code> 对象的 <code>run</code> 方法；否则，该方法不执行任何操作并返回。</p>
<p><code>Thread</code> 的子类应该重写该方法。</p>
<p>run()方法只是类的一个普通方法而已，如果直接调用Run方法，程序中依然只有主线程这一个线程，其程序执行路径还是只有一条，还是要顺序执行，还是要等待run方法体执行完毕后才可继续执行下面的代码，这样就没有达到写线程的目的。</p>
</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (target != <span class="keyword">null</span>) &#123;</span><br><span class="line">        target.run();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>注意：</p>
<ul>
<li>run（）方法只是重写了Runnable接口的一个普通方法而已，不会开启一个新的线程，只会在main线程中执行</li>
</ul>

      
      <!-- reward -->
      
      <div id="reward-btn">
        打赏
      </div>
      
    </div>
      <!-- copyright -->
      
        <div class="declare">
          <ul class="post-copyright">
            <li>
              <i class="ri-copyright-line"></i>
              <strong>版权声明： </strong s>
              本博客所有文章除特别声明外，均采用 <a href="https://www.apache.org/licenses/LICENSE-2.0.html" rel="external nofollow"
                target="_blank">Apache License 2.0</a> 许可协议。转载请注明出处！
            </li>
          </ul>
        </div>
        
    <footer class="article-footer">
      
          
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://zhuuu.work/2020/04/17/JUC/JUC-01-%E7%AE%80%E4%BB%8B%E4%B8%8E%E9%94%81%E5%8D%87%E7%BA%A7/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JUC/" rel="tag">JUC</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/" rel="tag">多线程</a></li></ul>


    </footer>

  </div>

  
  
  <nav class="article-nav">
    
      <a href="/2020/04/18/JUC/JUC-02-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            JUC-02-生产者消费者
          
        </div>
      </a>
    
    
      <a href="/2020/04/17/Communication/%E9%80%9A%E4%BF%A1%E5%8E%9F%E7%90%86%E7%AC%94%E8%AE%B0-11-%E5%B7%AE%E9%94%99%E6%8E%A7%E5%88%B6%E7%BC%96%E7%A0%81/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">通信原理-11-差错控制编码</div>
      </a>
    
  </nav>


  

  
  
<!-- valine评论 -->
<div id="vcomments-box">
    <div id="vcomments">
    </div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src='https://cdn.jsdelivr.net/npm/valine@1.3.10/dist/Valine.min.js'></script>
<script>
    new Valine({
        el: '#vcomments',
        notify: false,
        verify: '',
        app_id: '',
        app_key: '',
        path: window.location.pathname,
        avatar: 'mp',
        placeholder: '给我的文章加点评论吧~',
        recordIP: true
    });
    const infoEle = document.querySelector('#vcomments .info');
    if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
        infoEle.childNodes.forEach(function (item) {
            item.parentNode.removeChild(item);
        });
    }
</script>
<style>
    #vcomments-box {
        padding: 5px 30px;
    }

    @media screen and (max-width: 800px) {
        #vcomments-box {
            padding: 5px 0px;
        }
    }

    #vcomments-box #vcomments {
        background-color: #fff;
    }

    .v .vlist .vcard .vh {
        padding-right: 20px;
    }

    .v .vlist .vcard {
        padding-left: 10px;
    }
</style>

  

  
  
<div class="gitalk" id="gitalk-container"></div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1.5.0/dist/gitalk.css">


<script src="https://cdn.jsdelivr.net/npm/gitalk@1.5.0/dist/gitalk.min.js"></script>


<script src="https://cdn.jsdelivr.net/npm/blueimp-md5@2.10.0/js/md5.min.js"></script>

<script type="text/javascript">
  var gitalk = new Gitalk({
    clientID: 'db188ed8c86dc4b0dbf3',
    clientSecret: 'a58f92160e5a9efd726b7d533000a0737f3e3f3e',
    repo: 'Blog-comments',
    owner: 'Zhuuuuuuuu',
    admin: ['Zhuuuuuuuu'],
    // id: location.pathname,      // Ensure uniqueness and length less than 50
    id: md5(location.pathname),
    distractionFreeMode: false,  // Facebook-like distraction free mode
    pagerDirection: 'last'
  })

  gitalk.render('gitalk-container')
</script>

  

</article>
</section>
      <footer class="footer">
  <div class="outer">
    <ul class="list-inline">
      <li>
        &copy;
        2019-2021
        Zhuuu
      </li>
      <li>
        
      </li>
    </ul>
    <ul class="list-inline">
      <li>
        
        
        <span>
  <i>PV:<span id="busuanzi_value_page_pv"></span></i>
  <i>UV:<span id="busuanzi_value_site_uv"></span></i>
</span>
        
      </li>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
    <div class="to_top">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>
      </div>
    </main>
      <aside class="sidebar">
        <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="朱酱酱的学习博客"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/JVM/">JVM</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/JDK%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/">JDK源码</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">多线程</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Mysql/">Mysql</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Redis/">Redis</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E8%AE%BE%E8%AE%A1%E8%80%85%E6%A8%A1%E5%BC%8F/">设计模式</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/MyBatis/">MyBatis</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/SpringMVC/">SpringMVC</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Spring/">Spring</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/SpringBoot/">SpringBoot</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/">Linux</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/Leetcode/">Leetcode</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E5%89%8D%E7%AB%AF/">前端</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B/">网络编程</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/photoshop/">photoshop</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="http://smartzhuuu.lofter.com/" target="_blank" rel="noopener">摄影</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/2020/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="Search">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
      </aside>
      <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/images/alipay.jpg">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/images/wechat.jpg">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
      
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/jquery.justifiedGallery.min.js"></script>


<script src="/js/lazyload.min.js"></script>


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<script src="/js/share.js"></script>



<script src="/fancybox/jquery.fancybox.min.js"></script>




<script>
  try {
    var typed = new Typed("#subtitle", {
    strings: ['昨夜西风凋碧树。独上高楼，望尽天涯路','衣带渐宽终不悔，为伊消得人憔悴。','众里寻他千百度。蓦然回首，那人却在，灯火阑珊处。'],
    startDelay: 0,
    typeSpeed: 200,
    loop: true,
    backSpeed: 100,
    showCursor: true
    });
  } catch (err) {
  }
  
</script>




<script src="/js/tocbot.min.js"></script>

<script>
  // Tocbot_v4.7.0  http://tscanlin.github.io/tocbot/
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer:'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto',
    onClick: (e) => {
      $('.toc-link').removeClass('is-active-link');
      $(`a[href=${e.target.hash}]`).addClass('is-active-link');
      $(e.target.hash).scrollIntoView();
      return false;
    }
  });
</script>


<script>
  var ayerConfig = {
    mathjax: true
  }
</script>


<script src="/js/ayer.js"></script>


<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">


<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>



<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
      tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
      }
  });

  MathJax.Hub.Queue(function() {
      var all = MathJax.Hub.getAllJax(), i;
      for(i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
      }
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/mathjax@2.7.6/unpacked/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>


<script type="text/javascript" src="https://js.users.51.la/20544303.js"></script>
  </div>
</body>

</html>